package com.ruoyi.game.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.account.domain.Account;
import com.ruoyi.account.domain.AccountTrade;
import com.ruoyi.account.mapper.AccountMapper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.GameEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.UserContext;
import com.ruoyi.game.domain.bo.BallGroupScoreBO;
import com.ruoyi.game.domain.bo.BallScoreBO;
import com.ruoyi.game.domain.entity.*;
import com.ruoyi.game.mapper.GameWingoRecordMapper;
import com.ruoyi.game.service.*;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class LotteryServiceImpl implements ILotteryService {

    @Resource
    private IUserPick3RecordService userPick3RecordService;
    @Resource
    private IUserWingoRecordService userWingoRecordService;
    @Resource
    private IGamePick3RecordService gamePick3RecordService;
    @Resource
    private IGameWingoRecordService gameWingoRecordService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private GameWingoRecordMapper gameWingoRecordMapper;
    // pick3所有开奖结果
    private final List<int[]> PICK3_LIST = new ArrayList<>();

    private final Pick3Config pick3Config = new Pick3Config();

    private final WingoConfig wingoConfig = new WingoConfig();

    private BigDecimal compensation;

    @PostConstruct
    public void init() {
        pick3Config.setExpectRate(new BigDecimal("0.1"));
        wingoConfig.setExpectRate(new BigDecimal("0.1"));
        for (int A = 0; A <= 9; A++) {
            for (int B = 0; B <= 9; B++) {
                for (int C = 0; C <= 9; C++) {
                    PICK3_LIST.add(new int[]{A,B,C});
                }
            }
        }
    }

    @Override
    public void statWingo(String roundNo,Integer timeLimit) {
        String lockKey = "LOCK:STATWINGO:"+roundNo;
        boolean flag = redisCache.tryLock(lockKey, 60);
        if(!flag){
            return;
        }
        try {
            //查看是否有用户投注记录  没有则进行随机开奖
            List<UserWingoRecord> data = userWingoRecordService.getNotEndWingoRecord(roundNo);
            System.out.println("投注记录"+data.size());
            if(CollUtil.isEmpty(data)){
                // TODO 随机开奖
                String[] ballNos = lotteryDefaultWingo(null);
                gameWingoRecordService.updateLottery(roundNo, ballNos);
                //插入随机开奖数据  开奖记录
                GameWingoRecord gameWingoRecord = new GameWingoRecord();
                gameWingoRecord.setRoundNo(roundNo);
                gameWingoRecord.setFirstNo(ballNos[0]);
                gameWingoRecord.setSecondNo(ballNos[1]);
                gameWingoRecord.setThirdNo(ballNos[2]);
                gameWingoRecord.setToEnd(Constants.NUM_ONE);
                gameWingoRecord.setTimeLimit(timeLimit);
                gameWingoRecord.setCreateTime(DateUtils.getNowDate());
                Random random = new Random();
                gameWingoRecord.setLotteryResult(String.valueOf(random.nextInt(10))); // 生成一个0到9的随机整数
                gameWingoRecordMapper.insertGameWingoRecord(gameWingoRecord);
                return;
            }

            // 总支付金额
            BigDecimal totalPayAmount = data.stream().map(item -> item.getTotalAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
            // 获取开球池
            List<BallScoreBO> ballPool = getBallPool(data);
            // 确认开球数
            String ballNo = confirmBall(ballPool,wingoConfig,totalPayAmount);
            // 设置结束状态
            setEnd(data,ballNo);
            System.out.println("11111111111111111111");
            // TODO插入开奖记录
            //插入随机开奖数据  开奖记录
            GameWingoRecord gameWingoRecord = new GameWingoRecord();
            String[] ballNos = lotteryDefaultWingo(ballNo);
            gameWingoRecord.setRoundNo(roundNo);
            gameWingoRecord.setFirstNo(ballNos[0]);
            gameWingoRecord.setSecondNo(ballNos[1]);
            gameWingoRecord.setThirdNo(ballNos[2]);
            gameWingoRecord.setToEnd(Constants.NUM_ONE);
            gameWingoRecord.setTimeLimit(timeLimit);
            gameWingoRecord.setCreateTime(DateUtils.getNowDate());
            gameWingoRecord.setLotteryResult(ballNo); // 开奖结果
            gameWingoRecordMapper.insertGameWingoRecord(gameWingoRecord);
//            boolean isOk = gameWingoRecordService.updateLottery(roundNo, lotteryDefaultWingo(ballNo));
//            System.out.println("222222222222222222222");
//            if(!isOk){
//                System.out.println("6666666666666666666");
//                return;
//            }
            // 批量更新数据
            CollUtil.split(data,1000).forEach(batchList ->{
                System.out.println("3333333333333333333");
                // 一批一批更新
                userWingoRecordService.updateBatch(batchList);

            });



        }finally {
            redisCache.releaseLock(lockKey);
        }


    }

    @Override
    public void statPick3(String roundNo) {
        String lockKey = "LOCK:STATPICK3:"+roundNo;
        boolean flag = redisCache.tryLock(lockKey, 60);
        if(!flag){
            return;
        }
        try {
            List<UserPick3Record> data = userPick3RecordService.getNotEndPick3Record(roundNo);
            if(CollUtil.isEmpty(data)){
                // TODO 随机开奖
                int ballNos[] = lotteryDefaultPick3();
                gamePick3RecordService.updateLottery(roundNo, ballNos);
                return;
            }
            // 总支付金额
            BigDecimal totalPayAmount = data.stream().map(item -> item.getTotalAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
            // 获取开球池
            List<BallGroupScoreBO> ballPool = getPick3Pool(data);
            // 确认开球数
            int ballNos[] = confirmBall(ballPool,pick3Config,totalPayAmount);
            // 设置结束状态
            setEnd(data,ballNos);
            // TODO插入开奖记录
            boolean isOk = gamePick3RecordService.updateLottery(roundNo, ballNos);
            if(!isOk){
                return;
            }
            // 批量更新数据
            CollUtil.split(data,1000).forEach(batchList ->{
                // 一批一批更新
                userPick3RecordService.updateBatch(batchList);
                // 发送奖励
            });
        }finally {
            redisCache.releaseLock(lockKey);
        }
    }

    /**
     * 获取开球池（发放奖励越小的越排前面）
     * @param data
     * @return
     */
    private List<BallGroupScoreBO> getPick3Pool(List<UserPick3Record> data){
        List<BallGroupScoreBO> ballScore = new ArrayList<>();
        for(int []arr : PICK3_LIST){
            BigDecimal totalAmount = groupCalculateBall(String.valueOf(arr[0]),String.valueOf(arr[1]),String.valueOf(arr[2]),data);
            // 收集各个球的结果奖励
            ballScore.add(new BallGroupScoreBO(arr,totalAmount));
        }
        ballScore = CollUtil.sort(ballScore,(o1, o2) -> o1.getScore().compareTo(o1.getScore()));
        return ballScore;
    }

    private BigDecimal groupCalculateBall(String aBallNo,String bBallNo,String cBallNo,List<UserPick3Record> data){
        BigDecimal sum = BigDecimal.ZERO;
        for(UserPick3Record userPick3Record : data){
            BigDecimal pickMulX = GameEnum.Ball.getPick3MulX(aBallNo,bBallNo,cBallNo, userPick3Record.getaBallNo(), userPick3Record.getbBallNo(), userPick3Record.getcBallNo());
            BigDecimal  bonusAmount = NumberUtil.mul(userPick3Record.getTotalAmount(),pickMulX);
            sum = sum.add(bonusAmount);
        }
        return sum;
    }

    /**
     * 设置状态
     * @param data
     * @param ballNo
     */
    private void setEnd(List<UserWingoRecord> data,String ballNo){
        GameEnum.Ball ball = GameEnum.Ball.get(ballNo);
        for(UserWingoRecord item : data){
            GameEnum.PlayTypeGroupEnum playTypeGroup = GameEnum.PlayTypeGroupEnum.get(item.getPlayType());
            BigDecimal winAmount = NumberUtil.mul(getBonusMulX(ball,item,playTypeGroup),item.getTotalAmount());
            boolean toWin = winAmount.compareTo(BigDecimal.ZERO) > 0;
            item.setLotteryResult(ballNo);
            item.setToEnd(Constants.NUM_ONE);
            item.setToWin(toWin ? Constants.NUM_ONE : Constants.NUM_ZERO);
            item.setWinAmount(winAmount);
        }
    }

    /**
     * 设置状态
     * @param data
     * @param ballNos
     */
    private void setEnd(List<UserPick3Record> data,int ballNos[]){
        for(UserPick3Record item : data){
            item.setToEnd(Constants.NUM_ONE);
            BigDecimal winAmount = NumberUtil.mul(GameEnum.Ball.getPick3MulX(String.valueOf(ballNos[0]),String.valueOf(ballNos[1]),String.valueOf(ballNos[2]), item.getaBallNo(), item.getbBallNo(), item.getcBallNo()),item.getTotalAmount());
            boolean toWin = winAmount.compareTo(BigDecimal.ZERO) > 0;
            item.setToWin(toWin ? Constants.NUM_ONE : Constants.NUM_ZERO);
            item.setWinAmount(winAmount);
        }
    }

    private String confirmBall(List<BallScoreBO> ballPool,WingoConfig wingoConfig,BigDecimal totalPayAmount){
        Random random = new Random();
        int pool = random.nextInt(10);
        String poolNumber = String.valueOf(pool);
        compensation = null;


        System.out.println("开球11:"+poolNumber);
        for (int i =0;i<ballPool.size();i++){
            if (pool==Integer.valueOf(ballPool.get(i).getBallNo())){
                compensation = ballPool.get(i).getScore();
            }
        }

        if (totalPayAmount.subtract(compensation).compareTo(BigDecimal.ZERO)>0){

            //大于0 则是系统赚
            System.out.println("开球22:"+poolNumber);
            return poolNumber;
        }

        for (int i = 9;i>=0;i--){
            if (totalPayAmount.subtract(ballPool.get(i).getScore()).compareTo(BigDecimal.ZERO)>=0){
                poolNumber = ballPool.get(i).getBallNo();
                compensation = ballPool.get(i).getScore();
                System.out.println("开球结果是"+poolNumber);
                return poolNumber;
            }
        }

        poolNumber = ballPool.get(0).getBallNo();
        compensation = ballPool.get(0).getScore();
        System.out.println("开球结果是*"+poolNumber);
//
//        System.out.println("------------开球列表----------"+ JSON.toJSONString(ballPool));
//        if(wingoConfig.getExpectRate().compareTo(BigDecimal.ZERO) <= 0){
//            return ballPool.get(0).getBallNo();
//        }
//        // 期望奖金数      比例*支付总金额
//        BigDecimal expectAmount = NumberUtil.mul(wingoConfig.getExpectRate(),totalPayAmount);
//        System.out.println("------------支付总金额----------"+totalPayAmount);
//        System.out.println("------------期望奖金数----------"+expectAmount);
//        if(expectAmount.compareTo(totalPayAmount) > 0){
//            expectAmount = totalPayAmount;
//        }
//        // 查询最接近期望值的小的那个值
//        int ballIndex = 0;
//        for(int i = 0 ; i < ballPool.size() ; i++){
//
//            if(expectAmount.compareTo(ballPool.get(i).getScore()) >= 0){
//                //期望奖金数大于或者等于某一个球的奖金数
//                System.out.println("------------期望奖金数对比每个球的奖金----------"+expectAmount+"/"+ballPool.get(i).getScore());
//                ballIndex = i;
//                break;
//            }
//        }
//        // 最接近期望值的小的那个值
//        BallScoreBO ballScore = ballPool.get(ballIndex);
//        System.out.println("------------最接近期望值的小的那个值----------"+ballScore.getBallNo());
//        // 不允许出现奖金数大于支付金额的情况
//        System.out.println("------------奖金数与支付金额对比----------"+ballScore.getScore()+"//"+totalPayAmount);
//        if(ballScore.getScore().compareTo(totalPayAmount) > 0){
//            throw new RuntimeException("系统异常，请稍后再试");
//        }
        return poolNumber;
    }

    private int[] confirmBall(List<BallGroupScoreBO> ballPool, Pick3Config pick3Config, BigDecimal totalPayAmount){
        if(pick3Config.getExpectRate().compareTo(BigDecimal.ZERO) <= 0){
            return ballPool.get(0).getBallNos();
        }
        // 期望奖金数
        BigDecimal expectAmount = NumberUtil.mul(pick3Config.getExpectRate(),totalPayAmount);
        if(expectAmount.compareTo(totalPayAmount) > 0){
            expectAmount = totalPayAmount;
        }
        // 查询最接近期望值的小的那个值
        BigDecimal preAmount = ballPool.get(0).getScore();
        int ballIndex = 0;
        for(int i = 1 ; i < ballPool.size() ; i++){
            if(expectAmount.compareTo(ballPool.get(i).getScore()) >= 0){
                ballIndex = i-1;
                break;
            }
        }
        // 最接近期望值的小的那个值
        BallGroupScoreBO ballScore = ballPool.get(ballIndex);
        // 不允许出现奖金数大于支付金额的情况
        if(ballScore.getScore().compareTo(totalPayAmount) > 0){
            throw new RuntimeException("系统异常，请稍后再试");
        }
        return ballScore.getBallNos();
    }

    /**
     * 获取开球池（发放奖励越小的越排前面）
     * @param data
     * @return
     */
    private List<BallScoreBO> getBallPool(List<UserWingoRecord> data){
        List<BallScoreBO> ballScore = new ArrayList<>();
        for(GameEnum.Ball ball : GameEnum.Ball.values()){
            // 分组统计奖励
            Map<Integer,BigDecimal> groupStat = preCalculateBall(ball,data);
            // 统计奖励
            BigDecimal totalAmount = groupStat.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
            // 收集各个球的结果奖励
            ballScore.add(new BallScoreBO(ball.getValue(),totalAmount));
        }
        ballScore = CollUtil.sort(ballScore,(o1, o2) -> o1.getScore().compareTo(o2.getScore()));

        System.out.println("---------------各个球开奖结果----------------"+JSON.toJSONString(ballScore));
        return ballScore;
    }

    /**
     * 预计算某个球需要发放的奖励
     * @param ball
     * @param data
     * @return
     */
    private Map<Integer,BigDecimal> preCalculateBall(GameEnum.Ball ball,List<UserWingoRecord> data){
        // 分组
        Map<Integer,List<UserWingoRecord>> wingoMapList = data.stream().collect(Collectors.groupingBy(UserWingoRecord::getPlayType));
        Map<Integer,BigDecimal> result = new HashMap<>();
        // 统计
        for(GameEnum.PlayTypeGroupEnum groupEnum : GameEnum.PlayTypeGroupEnum.values()){
            List<UserWingoRecord> wingoRecordList = wingoMapList.get(groupEnum.getType());
            if(CollUtil.isEmpty(wingoRecordList)){
                result.put(groupEnum.getType(),BigDecimal.ZERO);
                continue;
            }
            // 预统计奖励
            BigDecimal bonusAmount = preStatBonus(wingoRecordList,ball,groupEnum);
            result.put(groupEnum.getType(),bonusAmount);
        }
        return result;
    }

    /**
     * 统计分组奖励
     * @param data
     * @param ball
     * @param groupEnum
     * @return
     */
    private BigDecimal preStatBonus(List<UserWingoRecord> data, GameEnum.Ball ball, GameEnum.PlayTypeGroupEnum groupEnum){
        BigDecimal sumAmount = BigDecimal.ZERO;
        for(UserWingoRecord item : data){
            BigDecimal bonusMulX = getBonusMulX(ball,item,groupEnum);
            if(bonusMulX.compareTo(BigDecimal.ZERO) <= 0){
                continue;
            }
            BigDecimal bonusAmount = NumberUtil.mul(item.getTotalAmount(),bonusMulX);
            sumAmount = NumberUtil.add(sumAmount,bonusAmount);
        }
        return sumAmount;
    }

    /**
     * 获取奖励倍数
     * @param ball
     * @param data
     * @param groupEnum
     * @return
     */
    private BigDecimal getBonusMulX(GameEnum.Ball ball,UserWingoRecord data,GameEnum.PlayTypeGroupEnum groupEnum){
        switch (groupEnum){
            case COLOR:
                return GameEnum.Ball.getColorMulX(ball,getpalyTyple( data.getPlayValue()));
            case SMALLBIG:
                return GameEnum.Ball.getSmallBigMulX(ball,getpalyTyple(data.getPlayValue()));
            case NUM:
                return GameEnum.Ball.getNumMulX(ball,data.getPlayValue());
            default:
                return BigDecimal.ZERO;
        }
    }

    private int[] lotteryDefaultPick3(){
        int i = RandomUtil.randomInt(0,PICK3_LIST.size()-1);
        return PICK3_LIST.get(i);
    }

    private String[] lotteryDefaultWingo(String ballNo){
        int a = RandomUtil.randomInt(0,GameEnum.Ball.values().length-1);
        int b = RandomUtil.randomInt(0,GameEnum.Ball.values().length-1);
        int c = RandomUtil.randomInt(0,GameEnum.Ball.values().length-1);

        List<GameEnum.Ball> balls = Arrays.stream(GameEnum.Ball.values()).collect(Collectors.toList());
        String lastBallNo = StrUtil.isNotBlank(ballNo) ? ballNo : balls.get(c).getValue();
        return new String[]{balls.get(a).getValue(),balls.get(b).getValue(),lastBallNo};
    }

    private int getpalyTyple(String content){
        int playTyple = 1;
        switch (content){
            case "red":
                playTyple = 1;
                break;
            case "violet":
                playTyple =2;
                break;
            case "green":
                playTyple = 3;
                break;
            case "big":
                playTyple = 5;
                break;
            case "small":
                playTyple = 4;
                break;
        }
        return playTyple;
    }
}
